package elijah.base.thread;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;

import com.google.common.base.Objects;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.jeremyliao.liveeventbus.LiveEventBus;

import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ConcurrentMap;

import elijah.base.AppBase;
import elijah.base.utils.ClassUtils;
import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;

public class ScheduledProvider extends FragmentManager.FragmentLifecycleCallbacks implements Application.ActivityLifecycleCallbacks, View.OnAttachStateChangeListener {
    public static final String FLEX_RATE = "FLEX_RATE:";
    private final ListMultimap<String, ScheduledTask> taskMap = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());

    private final ConcurrentMap<String, CompositeDisposable> disposableMap = Maps.newConcurrentMap();

    private final ListMultimap<String, ScheduledTask> flexTaskMap = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
    private static volatile ScheduledProvider mInstance;

    private Activity currentActivity;

    public Activity getCurrentActivity() {
        return currentActivity;
    }

    public synchronized static ScheduledProvider instance() {
        if (mInstance == null) {
            synchronized (AppBase.class) {
                if (mInstance == null) {
                    mInstance = new ScheduledProvider();
                }
            }
        }
        return mInstance;
    }

    private ScheduledProvider() {
    }


    @Override
    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) {
        if (!(activity instanceof AppCompatActivity)) {
            return;
        }
        ((AppCompatActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(this, true);
        initSchedules(activity);
        scheduled(Scheduled.Scope.ALIVE, activity);

    }


    @Override
    public void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull View v, @Nullable Bundle savedInstanceState) {
        super.onFragmentViewCreated(fm, f, v, savedInstanceState);
        initSchedules(f);
        scheduled(Scheduled.Scope.ALIVE, f);

    }

    @Override
    public void onActivityStarted(@NonNull Activity activity) {

    }

    @Override
    public void onActivityResumed(@NonNull Activity activity) {
        currentActivity = activity;

        if (!(activity instanceof AppCompatActivity)) {
            return;
        }
        scheduled(Scheduled.Scope.FRONT, activity);
    }

    @Override
    public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {
        super.onFragmentResumed(fm, f);
        scheduled(Scheduled.Scope.FRONT, f);
    }

    @Override
    public void onActivityPaused(@NonNull Activity activity) {
        if (Objects.equal(activity, currentActivity)) {
            currentActivity = null;
        }

        if (!(activity instanceof AppCompatActivity)) {
            return;
        }
        disposable(Scheduled.Scope.FRONT, activity);

    }

    @Override
    public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {
        super.onFragmentPaused(fm, f);
        disposable(Scheduled.Scope.FRONT, f);
    }

    @Override
    public void onActivityStopped(@NonNull Activity activity) {


    }

    @Override
    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {

    }


    @Override
    public void onActivityDestroyed(@NonNull Activity activity) {
        if (!(activity instanceof AppCompatActivity)) {
            return;
        }
        ((AppCompatActivity) activity).getSupportFragmentManager().unregisterFragmentLifecycleCallbacks(this);
        disposable(Scheduled.Scope.ALIVE, activity);
    }

    @Override
    public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {
        super.onFragmentViewDestroyed(fm, f);
        disposable(Scheduled.Scope.ALIVE, f);
    }


    private void initSchedules(@NonNull Object obj) {


        List<Method> methods = ClassUtils.getAnnotation(obj.getClass(), Scheduled.class);
        for (Method method : methods) {
            Scheduled scheduled = method.getAnnotation(Scheduled.class);
            taskMap.get(hash(obj) + "-" + scheduled.scope()).add(setTask(new ScheduledTask(obj, method), scheduled));

        }
    }

    private void scheduled(int scope, Object obj) {
        String key = hash(obj) + "-" + scope;
        if (!taskMap.containsKey(key)) {
            return;
        }
        List<ScheduledTask> tasks = taskMap.get(key);
        if (tasks.size() <= 0) {
            return;
        }
        CompositeDisposable disposable = disposableMap.get(key);
        if (disposable == null) {
            disposable = new CompositeDisposable();
            disposableMap.put(key, disposable);
        }

        for (ScheduledTask task : tasks) {
            if (task.getFixedDelay() > 0) {
                task.setThread(Observable.timer(task.getFixedDelay(), task.getTimeUnit()).compose(Threads.io2main()).subscribe(d -> task.getMethod().invoke(obj), Throwable::printStackTrace));

            } else if (task.getFixedRate() > 0) {
                task.setThread(Observable.interval(task.getInitialDelay(), task.getFixedRate(), task.getTimeUnit()).compose(Threads.io2main()).subscribe(aLong -> task.getMethod().invoke(obj), Throwable::printStackTrace));
            } else if (!TextUtils.isEmpty(task.getFlexTag())) {
                task.setThread(Observable.interval(task.getFlexRate(), task.getFlexRate(), task.getTimeUnit()).compose(Threads.io2main()).subscribe(aLong -> task.getMethod().invoke(obj), Throwable::printStackTrace));
                flexTaskMap.put(FLEX_RATE + task.getFlexTag(), task);
            }
            if (task.getThread() != null && !task.getThread().isDisposed()) {
                disposable.add(task.getThread());
            }
        }

    }

    private void disposable(int scope, Object obj) {
        synchronized (disposableMap) {
            CompositeDisposable disposable = disposableMap.get(hash(obj) + "-" + scope);
            if (disposable != null) {
                disposable.clear();
            }
        }

    }

    public ScheduledTask setTask(ScheduledTask task, Scheduled scheduled) {

        task.setFixedDelay(scheduled.fixedDelay()).setFixedRate(scheduled.fixedRate()).setFlexTag(scheduled.flexTag()).setInitialDelay(scheduled.initialDelay()).setScope(scheduled.scope()).setTimeUnit(scheduled.timeUnit()).setFlexRate(AppBase.get().storage().getLong(FLEX_RATE + scheduled.flexTag(), scheduled.initialFlexRate()));

        if (!TextUtils.isEmpty(scheduled.flexTag())) {

            LiveEventBus.get(FLEX_RATE + scheduled.flexTag(), Long.class).observeForever(value -> {
                AppBase.get().storage().putLong(FLEX_RATE + scheduled.flexTag(), value);
                List<ScheduledTask> scheduledTasks = flexTaskMap.get(FLEX_RATE + scheduled.flexTag());
                for (ScheduledTask item : scheduledTasks) {
                    item.setFlexRate(value);
                    Disposable thread = item.getThread();
                    if (thread != null && !thread.isDisposed()) {
                        thread.dispose();
                        item.setThread(Observable.interval(item.getFlexRate(), item.getFlexRate(), item.getTimeUnit()).compose(Threads.io2main()).subscribe(aLong -> item.getMethod().invoke(item.getObject()), Throwable::printStackTrace));
                        disposableMap.get(item.getObject() + "-" + scheduled.scope()).add(item.getThread());
                    }
                }
            });

        }

        return task;

    }

    @Override
    public void onViewAttachedToWindow(View view) {
        initSchedules(view);
        scheduled(Scheduled.Scope.FRONT, view);
        scheduled(Scheduled.Scope.ALIVE, view);
    }

    @Override
    public void onViewDetachedFromWindow(View view) {
        view.removeOnAttachStateChangeListener(this);
        disposable(Scheduled.Scope.FRONT, view);
        disposable(Scheduled.Scope.ALIVE, view);

    }

    public static String hash(Object obj) {
        return String.valueOf(System.identityHashCode(obj));
    }
}
